package ch.uzh.ifi.seal.ssqa.ui.views;


import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;

import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.draw2d.Label;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
import org.eclipse.gmf.runtime.emf.core.resources.GMFResource;
import org.eclipse.jface.dialogs.ErrorDialog;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.part.ViewPart;
import org.eclipse.zest.core.widgets.Graph;
import org.eclipse.zest.core.widgets.GraphConnection;
import org.eclipse.zest.core.widgets.GraphNode;
import org.eclipse.zest.layouts.LayoutAlgorithm;
import org.eclipse.zest.layouts.LayoutStyles;
import org.eclipse.zest.layouts.algorithms.SpringLayoutAlgorithm;

import ch.uzh.ifi.seal.ssqa.SsqaPlugin;
import ch.uzh.ifi.seal.ssqa.metrics.ChangeCoupling;
import ch.uzh.ifi.seal.ssqa.model.Diagram;
import ch.uzh.ifi.seal.ssqa.model.DiagramComponent;
import ch.uzh.ifi.seal.ssqa.model.DiagramComposite;
import ch.uzh.ifi.seal.ssqa.model.Source;
import ch.uzh.ifi.seal.ssqa.preferences.PreferenceConstants;
import ch.uzh.ifi.seal.ssqa.storage.IStorage;
import ch.uzh.ifi.seal.ssqa.storage.StorageResult;
import ch.uzh.ifi.seal.ssqa.storage.TeneoHibernateStorage;
import ch.uzh.ifi.seal.ssqa.ui.figures.SsqaGraphNode;
import ch.uzh.ifi.seal.ssqa.util.ColorHelper;
import ch.uzh.ifi.seal.ssqa.util.ValueNormalizer;

public class ChangeCouplingView extends ViewPart  {
	
	public static final String ID = "ch.uzh.ifi.seal.ssqa.ui.views.ChangeCouplingView";
	
	private String resourceURI;

	private Integer threshold;
	
	private Graph graph; 
	
	private HashMap<String, GraphNode> nodes = new HashMap<String, GraphNode>();

	private Composite comp;
	
	public void createPartControl(Composite comp) {
		comp.setBackground( new Color(comp.getDisplay(), new RGB(255, 255,255)));
		IPreferenceStore store = SsqaPlugin.getDefault().getPreferenceStore();
		this.resourceURI = store.getString(PreferenceConstants.ANALYSIS_DEFAULT_ARCH_FILE);
		this.threshold = store.getInt(PreferenceConstants.ANALYSIS_COUPLING_THRESHOLD);
		this.comp = comp;
	}

	@Override
	public void setFocus() {}

	public void openChangeCouplingView(EObject component) {
		this.graph = new Graph(comp, SWT.NONE);
		if(component != null){
			this.getSourceNodes((DiagramComponent) component);
		} else {
			ResourceSet resSet = new ResourceSetImpl();           
			GMFResource resource = (GMFResource) resSet.getResource(URI.createPlatformResourceURI(this.resourceURI, true), true);  
			EList<EObject> content = resource.getContents();
			
			Diagram diagram = this.getDiagramNode(content);
			if(diagram != null){
				this.getDiagramComponentNodes(diagram);
			} else {
				Status status = new Status(IStatus.ERROR, SsqaPlugin.PLUGIN_ID, 0, "No Diagram found in the file: "+this.resourceURI, null);
				ErrorDialog.openError(PlatformUI.getWorkbench().getDisplay().getActiveShell(), "Diagram not found", "The Diagram could not be found in the GMFResource.", status);
			}
		}
		
		this.getConnectionNodes();
		this.hideUnconnectedNodes();

		LayoutAlgorithm layout = setLayout();
		this.graph.setBounds(this.comp.getBounds());
		this.graph.setLayoutAlgorithm(layout, true);
	}
	
	private Diagram getDiagramNode(EList<EObject> content){
		for(EObject object : content){
			if(object instanceof Diagram){
				return (Diagram)object;
			} 
		}
		return null;
	}
	
	private void getDiagramComponentNodes(Diagram diagram){
		for(DiagramComponent component : diagram.getComponents()){
			if(component instanceof DiagramComposite){
				this.getDiagramComponentChildren((DiagramComposite)component);
			} else {
				this.nodes.put(component.getIdentifier(), new GraphNode(this.graph, SWT.NONE, component.getName(), component));
			}
		}
	}
	
	private void getDiagramComponentChildren(DiagramComposite composite){
		this.nodes.put(composite.getIdentifier(), new GraphNode(this.graph, SWT.NONE, composite.getName(), composite));
		if(!composite.getChildren().isEmpty()){
			for(DiagramComponent component : composite.getChildren()){
				if(component instanceof DiagramComposite){
					this.getDiagramComponentChildren((DiagramComposite)component);
				} else {
					GraphNode node = new GraphNode(this.graph, SWT.NONE, component.getName(), component);
					this.nodes.put(component.getIdentifier(), node);
				}
			}
		}
	}
	
	private void getConnectionNodes(){
		ChangeCoupling coupling = new ChangeCoupling();
		HashMap<GraphConnection, Integer> couplingValues = new HashMap<GraphConnection, Integer>();
		List<GraphNode> nodeList = new ArrayList<GraphNode>();
		nodeList.addAll(this.nodes.values());
		while(nodeList.size()>1){
			GraphNode node = nodeList.remove(0);
			for(GraphNode node2 : nodeList){
				Integer couplingValue = coupling.getCoupling((EObject)node.getData(), (EObject)node2.getData());
				if(couplingValue != null && couplingValue > 0){
					GraphConnection connection = new GraphConnection(this.graph, SWT.NONE, node, node2);
					couplingValues.put(connection, couplingValue);
				}
			}
		}
		Collection<Integer> collectionValues = couplingValues.values();
		Integer[] values = couplingValues.values().toArray(new Integer[collectionValues.size()]);
		Arrays.sort(values);
		// FIXME: ArrayIndexOutOfBoundsException
		Integer min = values[0];
		Integer max = values[values.length-1];
		for(GraphConnection con : couplingValues.keySet()){
			double conWidth = ValueNormalizer.normalizeValue(couplingValues.get(con), max, min);
			if(conWidth > this.threshold){
				con.setLineWidth((int)(conWidth/2));
				con.setLineColor(ColorHelper.getNormalizedColor(conWidth));
				con.setTooltip(new Label("Coupling Value: "+couplingValues.get(con)));
			} else {
				con.dispose();
			}
		}
	}
	
	private void hideUnconnectedNodes(){
		for(GraphNode node : this.nodes.values()){
			if(node.getSourceConnections().isEmpty() && node.getTargetConnections().isEmpty()){
				node.dispose();
			}
		}
	}
	
	private void getSourceNodes(DiagramComponent component){
		this.nodes = new HashMap<String, GraphNode>();
		IStorage storage = new TeneoHibernateStorage();
		StorageResult result = storage.executeListQuery("FROM Source WHERE diagrammapping='"+component.getIdentifier()+"' ");
		List<Source> sources = (List<Source>) result.getListResult();
		for(Source source : sources){
			GraphNode node = new SsqaGraphNode(this.graph, SWT.NONE, source.getName(), source);
			node.setTooltip(new Label(source.getName()));
			this.nodes.put(source.getProjectPath(), node);
		}
	}
	
	private LayoutAlgorithm setLayout() {
		SpringLayoutAlgorithm layout = new SpringLayoutAlgorithm(LayoutStyles.NO_LAYOUT_NODE_RESIZING);
		return layout;
	}
}
